<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/4/8
 * Time: 14:25
 */

namespace Game\Services;


use Game\Lib\Game;
use Game\Traits\Singleton;
use Game\Lib\Redis_Const;
use Predis\Client;
use Game\Library\Redis;
use Game\Library\Logger;

class RedisService
{
    use Singleton;
    /** @var Client  */
    private $redis;

    private function redis(): Client{
        if (!($this->redis instanceof Redis)) {
            $this->redis = Redis::getInstance();
        }
        $redisResult = $this->redis->ping();
        if(!is_object($redisResult) || $redisResult->getPayload() != "PONG"){
            Logger::getInstance()->log( "function(redis):redis is not connect in file".__FILE__." on Line ".__LINE__,'error');
            $this->redis = Redis::getInstance();
        }
        return $this->redis;

    }
    /**
     * 设置房间状态   1等待中   2游戏中
     * @param $room_id
     * @param $pre_status
     * @param $update_status
     * @return bool
     */
    public function setRoomStatusTransaction($room_id,$pre_status,$update_status){
        $key = strtr(Redis_Const::Room_Key, array("[roomid]"=>$room_id));

        $success = false;
        $options = array(
            'cas'   => true,    // Initialize with support for CAS operations
            'watch' => $key,    // Key that needs to be WATCHed to detect changes
            'retry' => 3,       // Number of retries on aborted transactions, after
            // which the client bails out with an exception.
        );
        $this->redis()->transaction($options, function (Client $tx) use ($key,$pre_status,$update_status, &$success) {
            $room_status = $tx->hget($key, Redis_Const::Room_Field_Status);
            if (isset($room_status) && $room_status == $pre_status) {
                $tx->multi();   // With CAS, MULTI *must* be explicitly invoked.
                $tx->hmset($key, array(Redis_Const::Room_Field_Status => $update_status));
                $success =  true;
            } else {
                Logger::getInstance()->error( "function(setRoomStatusTransaction):room_status error ".$pre_status." in file".__FILE__." on Line ".__LINE__);
                $success =  false;
            }
        });
        return $success;
    }

    /**
     * 离开座位的加锁函数  (离线回调或观战方法调用)
     * @param $room_id
     * @param $account_id
     * @return int
     */
    public function leaveSeatTransaction($room_id,$account_id){
        $key = strtr(Redis_Const::RoomSequence_Key, array("[roomid]"=>$room_id));
        $success = 0;
        $options = array(
            'cas'   => true,    // Initialize with support for CAS operations
            'watch' => $key,    // Key that needs to be WATCHed to detect changes
            'retry' => 3,       // Number of retries on aborted transactions, after
            // which the client bails out with an exception.
        );
        $this->redis()->transaction($options, function (Client $tx) use ($key,$account_id, &$success) {
            $score = $tx->zscore($key,$account_id);
            $tx->zincrby($key,-2 * $score, $account_id);
//            $result = $tx->zrange($key,0,-1,'withscores');
//            if(is_array($result)) {
//                if(isset($result[$account_id])){
//                    $success = 1;
//                } else if(isset($result[-$account_id])){ //unchecked 离座
//                    $tx->zrem($key, -$account_id);
//                    $success = 2;
//                }
//            }
        });
        return $success;
    }



    /**
     * 进房并发
     * @param $room_id
     * @param $account_id
     * @param $max_count
     * @return bool
     */
    public function joinRoomTransaction($room_id, $account_id, $max_count) {
        $roomSequenceKey = strtr(Redis_Const::RoomSequence_Key, array("[roomid]"=>$room_id));
        $success = false;
        $options = array(
            'cas'   => true,    // Initialize with support for CAS operations
            'watch' => $roomSequenceKey,    // Key that needs to be WATCHed to detect changes
            'retry' => 3,       // Number of retries on aborted transactions, after
            // which the client bails out with an exception.
        );

        $this->redis()->transaction($options, function (Client $tx) use ($roomSequenceKey, $account_id, $max_count, &$success) {
            $result = $tx->zrange($roomSequenceKey,0,-1,'withscores');
            if(is_array($result)) {
                $account_count = count($result);
                if($account_count >= $max_count){
                    Logger::getInstance()->error("joinRoom: accountid".$account_id." 人数已满 ".__FILE__.__LINE__);
                    $success = false;
                } else {
                    if (isset($result[$account_id])){
                        $success = $result[$account_id];
                    }else {
                        $success = $account_count + 1;
                        $tx->multi();   // With CAS, MULTI *must* be explicitly invoked.
                        $tx->zadd($roomSequenceKey, $success, $account_id);
                    }
                }
            } else {
                Logger::getInstance()->error("joinRoom:zrange result error ".__FILE__.__LINE__);
                $success = false;
            }
        });
        return $success;
    }



    /**
     * 获取房间设置
     * @param $room_id
     * @param $field
     * @return array
     */
    public function queryRoomInfo($room_id, $field = false){
        $roomKey = strtr(Redis_Const::Room_Key, array("[roomid]"=>$room_id));
        if ($field) {
            $result = $this->redis()->hget($roomKey, $field);
        }else{
            $result = $this->redis()->hgetall($roomKey);
            if (!isset($result[Redis_Const::Room_Field_Scoreboard])) {
                $result[Redis_Const::Room_Field_Scoreboard] = "{}";
            }
        }
        return $result;
    }

    /**
     * 添加房间信息到redis
     * @param $room_id
     * @param $data
     */
    public function updateRoomInfo($room_id, $data){
        $roomKey = strtr(Redis_Const::Room_Key,  array("[roomid]"=>$room_id));
        $this->redis()->hmset($roomKey,$data);
    }

    /**
     * 获取房间设置
     * @param $room_id
     * @param $field
     * @return array
     */
    public function queryRoomRule($room_id, $field = false){
        $roomKey = strtr(Redis_Const::RoomRule_Key, array("[roomid]"=>$room_id));
        if ($field) {
            $result = $this->redis()->hget($roomKey, $field);
        }else{
            $result = $this->redis()->hgetall($roomKey);
        }
        return $result;
    }

    /**
     * 添加房间信息到redis
     * @param $room_id
     * @param $data
     */
    public function updateRoomRule($room_id, $data){
        $roomKey = strtr(Redis_Const::Room_Key,  array("[roomid]"=>$room_id));
        $this->redis()->hmset($roomKey,$data);
    }


    /**
     * 获取庄家account_id
     * @param $room_id
     * @return int
     */
    public function queryBanker($room_id){
        $roomKey = strtr(Redis_Const::Room_Key, array("[roomid]"=>$room_id));
        $result = $this->redis()->hget($roomKey,Redis_Const::Room_Field_Banker);
        return $result > 0 ? $result : -1;
    }

    /**
     * 获取房主id
     * @param $room_id
     * @return mixed
     */
    public function queryCreator($room_id){
        $roomKey = strtr(Redis_Const::Room_Key, array("[roomid]"=>$room_id));
        $result = $this->redis()->hget($roomKey,Redis_Const::Room_Field_Creator);
        return $result;
    }

    /**
     * 获取开局时间
     * @param $room_id
     * @return int|string
     */
    public function queryStartTime($room_id){
        $roomKey = strtr(Redis_Const::Room_Key, array("[roomid]"=>$room_id));
        $result = $this->redis()->hget($roomKey,Redis_Const::Room_Field_StartTime);
        return  $result > 0 ? $result : -1;
    }

    /**
     * 获取房间用户有序集合
     * @param $room_id
     * @param $withscores
     * @return array
     */
    public function queryRoomMembers($room_id, $withscores=['WITHSCORES'=>true]){
        $roomSequenceKey = strtr(Redis_Const::RoomSequence_Key, array("[roomid]"=>$room_id));
        $result = $this->redis()->zrange($roomSequenceKey,0,-1, $withscores);
        if(is_array($result) ){
            return $result;
        } else {
            return array();
        }
    }

    /**
     * 获取玩家的座位号  从1开始，false表示未入座
     * @param $room_id
     * @param $account_id
     * @return bool|int
     */
    public function querySerialNum($room_id, $account_id){
        $roomSequenceKey = strtr(Redis_Const::RoomSequence_Key, array("[roomid]"=>$room_id));
        $members = $this->redis()->zrange($roomSequenceKey, 0, -1, ['WITHSCORES'=>true]);
        if(isset($members[$account_id])){
            return $members[$account_id];
        } else {
            return false;
        }
    }

    /**
     * 获取用户是否已经看牌
     * @param $room_id
     * @param $account_id
     * @return int|array
     */
    public function querySeenCard($room_id, $account_id = false){
        $seenCardKey = strtr(Redis_Const::SeenCard_Key, array("[roomid]"=>$room_id));
        if ($account_id) {
            $result = $this->redis()->hget($seenCardKey, $account_id);
            return $result == 1 ? 1 : 0;
        }else{
            return $this->redis()->hgetall($seenCardKey);
        }
    }

    /**
     * 返回用户手牌
     * @param $room_id
     * @param $player_id
     * @return array|bool
     */
    public function queryPlayerCard($room_id, $player_id){
        $cardKey = strtr(Redis_Const::Card_Key, array("[roomid]"=>$room_id));
        $card_str = $this->redis()->hget($cardKey, $player_id);
        if(Redis_Const::DATA_NONEXISTENT === $card_str || ""==trim($card_str)){  //异常状态返回空数组
            return false;
        }
        return explode(",", $card_str);
    }

    /**
     * 更新用户手牌
     * @param $room_id
     * @param $player_id
     * @param $card_str
     */
    public function updatePlayerCard($room_id, $player_id, $card_str){
        $cardKey = strtr(Redis_Const::Card_Key, array("[roomid]"=>$room_id));
        $this->redis()->hset($cardKey, $player_id, is_array($card_str) ? implode(",", $card_str): $card_str);
    }

    /**
     * 获取准备的人
     * @param $room_id
     * @return array
     */
    public function queryReadyUser($room_id){
        $accountStatusKey = strtr(Redis_Const::AccountStatus_Key, array("[roomid]"=>$room_id));
        $ready_user_array = [];
        $result = $this->redis()->hgetall($accountStatusKey);
        if(Redis_Const::DATA_NONEXISTENT !== $result){
            foreach ($result as $account_id => $status) {
                if($status == Game::AccountStatus_Ready){
                    $ready_user_array[] = $account_id;
                }
            }
        }
        return $ready_user_array;
    }

    /**
     * 更新用户状态
     * @param $room_id
     * @param $account_id
     * @param $status
     * @return array
     */
    public function updateAccountStatus($room_id, $account_id, $status){
        $accountStatusKey = strtr(Redis_Const::AccountStatus_Key, array("[roomid]"=>$room_id));
        $this->redis()->hmset($accountStatusKey,[$account_id=>$status]);
        $this->writeLog("[$room_id] ($account_id) 状态改变 ". $status);
        //观战用户
        $this->updateLremGuestList($room_id, $account_id);
        //广播用户状态改变
        return array(
            'account_id' => $account_id,
            'account_status'=>$status,
            'online_status'=>$this->queryOnlineStatus($room_id, $account_id)
        );
    }

    /**
     * 获取准备人数
     * @param $room_id
     * @return int
     */
    public function queryReadyCount($room_id){
        $accountStatusKey = strtr(Redis_Const::AccountStatus_Key, array("[roomid]"=>$room_id));
        $result = $this->redis()->hgetall($accountStatusKey);
        $count = 0;
        if(Redis_Const::DATA_NONEXISTENT !== $result){
            foreach ($result as $account_id => $status) {
                if($status == Game::AccountStatus_Ready){
                    $count++;
                }
            }
        }
        return $count;
    }

    /**
     * 获取用户状态
     * @param $room_id
     * @param $account_id
     * @return mixed
     */
    public function queryAccountStatus($room_id, $account_id = false){
        $accountStatusKey = strtr(Redis_Const::AccountStatus_Key, array("[roomid]"=>$room_id));
        if ($account_id) {
            $result = $this->redis()->hget($accountStatusKey, $account_id);
        }else{
            $result = $this->redis()->hgetall($accountStatusKey);
        }
        return $result;
    }





    /**
     * 观战功能 获取房间观战者队列
     * @param $room_id
     * @return array
     */
    public function queryGuestList($room_id){
        $roomGuestKey = strtr(Redis_Const::RoomGuest_Key, array("[roomid]"=>$room_id));
        $guests = $this->redis()->lrange($roomGuestKey,0, -1);
        if(is_array($guests) ){
            return $guests;
        } else {
            return array();
        }
    }

    /**
     * 从房间删除观战用户
     * @param $room_id
     * @param $account_id
     * @param string $action
     */
    public function updateLremGuestList($room_id,$account_id){
        $roomGuestKey = strtr(Redis_Const::RoomGuest_Key, ['[roomid]'=>$room_id]);
        $this->redis()->lrem($roomGuestKey,0,$account_id);
    }

    /**
     * 获取房间参观者数量
     * @param $room_id
     * @return int
     */
    public function queryGuestCount($room_id){
        $roomGuestKey = strtr(Redis_Const::RoomGuest_Key,  array("[roomid]"=>$room_id));
        return  $this->redis()->llen($roomGuestKey);
    }

    /**
     * 获取叫分基准
     * @param $room_id
     * @return mixed
     */
    public function queryBenchmark($room_id){
        $playKey = strtr(Redis_Const::Play_Key, array("[roomid]"=>$room_id));
        return $this->redis()->hget($playKey,Redis_Const::Play_Field_Benchmark);
    }

    /**
     * 设置叫分基准
     * @param $room_id
     * @param $benchmark
     * @return bool
     */
    public function updateBenchmark($room_id, $benchmark){
        if($benchmark < 0){
            return false;
        }
        $playKey = strtr(Redis_Const::Play_Key, array("[roomid]"=>$room_id));
        return $this->redis()->hmset($playKey,[Redis_Const::Play_Field_Benchmark=>$benchmark]);
    }
    /**
     * 获取定时器id
     * @param $room_id
     * @return int
     */
    public function queryTimerId($room_id){
        $playKey = strtr(Redis_Const::Play_Key, array("[roomid]"=>$room_id));
        $result = $this->redis()->hget($playKey,Redis_Const::Play_Field_TimerId);
        return $result > 0 ? $result : -1;
    }
    public function queryTimerTime($room_id){
        $playKey = strtr(Redis_Const::Play_Key, array("[roomid]"=>$room_id));
        $result = $this->redis()->hget($playKey,Redis_Const::Play_Field_TimerTime);
        return $result > 0 ? $result : -1;
    }
    /**
     * 设置或取消(timer_id  -1)定时器
     * @param $room_id
     * @param $timer_id
     */
    public function updateTimer($room_id, $timer_id){
        $playKey = strtr(Redis_Const::Play_Key, array("[roomid]"=>$room_id));
        $mkv[Redis_Const::Play_Field_TimerId] = $timer_id;
        if($timer_id > 0){
            $mkv[Redis_Const::Play_Field_TimerTime] = time();
        } else {
            $mkv[Redis_Const::Play_Field_TimerTime] = -1;
        }
        $this->redis()->hmset($playKey,$mkv);
    }

    /**
     * 查询倒计时
     * @param $room_id
     * @param $limit
     * @return int
     */
    public function queryCountdown($room_id, $limit){
        $playKey = strtr(Redis_Const::Play_Key, array("[roomid]"=>$room_id));
        $timer_time = $this->redis()->hget($playKey,Redis_Const::Play_Field_TimerTime);
        if($timer_time > 0){
            $countdown = $limit - time() + $timer_time;
            return $countdown > 0 ? $countdown : 0;
        } else {
            return 0;
        }
    }

    /**
     * 获取激活的操作中的用户
     * @param $room_id
     * @return int|string
     */
    public function queryActiveUser($room_id){
        $playKey = strtr(Redis_Const::Play_Key, array("[roomid]"=>$room_id));
        $result = $this->redis()->hget($playKey,Redis_Const::Play_Field_ActiveUser);
        return  $result > 0 ? $result : -1;
    }
    /**
     * 获取分数池分数
     * @param $room_id
     * @return int
     */
    public function queryPoolScore($room_id){
        $playKey = strtr(Redis_Const::Play_Key, array("[roomid]"=>$room_id));
        $result = $this->redis()->hget($playKey,Redis_Const::Play_Field_PoolScore);
        return $result > 0 ? $result : 0;
    }

    /**
     * 查询用户积分
     * @param $room_id
     * @param bool|int $account_id
     * @return array|string
     */
    public function queryRoomScore($room_id, $account_id = false){
        $roomScoreKey = strtr(Redis_Const::RoomScore_Key, array("[roomid]"=>$room_id));
        if ($account_id){
            return $this->redis()->hget($roomScoreKey, $account_id);
        }else{
            return $this->redis()->hgetall($roomScoreKey);
        }
    }
    /**
     * 更新用户积分
     * @param $room_id
     * @param $array
     */
    public function updateRoomScore($room_id, $account_id, $score){
        $roomScoreKey = strtr(Redis_Const::RoomScore_Key, array("[roomid]"=>$room_id));
        $this->redis()->hmset($roomScoreKey, [$account_id => $score]);
    }

    public function pushPlayMember($room_id,$account_id, $pos = "left"){
        $key = strtr(Redis_Const::PlayMember_Key, array("[roomid]"=>$room_id));
        if ($pos == "left") {
            if (is_array($account_id)) {
                $this->redis()->lpushx($key, $account_id);
            }else{
                $this->redis()->lpush($key, $account_id);
            }
        }else{
            if (is_array($account_id)) {
                $this->redis()->rpushx($key, $account_id);
            }else{
                $this->redis()->rpush($key, $account_id);
            }
        }
    }



    public function deleteKey($key){
        $this->redis()->del($key);
    }

    public function deleteChipKey($room_id){
        $key = strtr(Redis_Const::Chip_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }
    public function deleteSeenCardKey($room_id){
        $key = strtr(Redis_Const::SeenCard_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }
    public function deleteCardKey($room_id){
        $key = strtr(Redis_Const::Card_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }
    public function deletePlayMemberKey($room_id){
        $key = strtr(Redis_Const::PlayMember_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }
    public function deleteAccountStatusKey($room_id){
        $key = strtr(Redis_Const::AccountStatus_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }
    public function deleteRoomKey($room_id){
        $key = strtr(Redis_Const::Room_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }

    public function deleteRoomScoreKey($room_id){
        $key = strtr(Redis_Const::RoomScore_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }
    public function deletePlayKey($room_id){
        $key = strtr(Redis_Const::Play_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }
    public function deleteRoomSequenceKey($room_id){
        $key = strtr(Redis_Const::RoomSequence_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }
    public function deleteRoomGuestKey($room_id){
        $key = strtr(Redis_Const::RoomGuest_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }
    public function deleteRoomRuleKey($room_id){
        $key = strtr(Redis_Const::RoomRule_Key, array("[roomid]"=>$room_id));
        $this->deleteKey($key);
    }

    /**
     * 清理房间Redis
     * @param $room_id
     */
    public function clearRoom($room_id){
        $this->deletePlayKey($room_id);
        $this->deleteRoomScoreKey($room_id);
        $this->deleteAccountStatusKey($room_id);
        $this->deletePlayMemberKey($room_id);
        $this->deleteCardKey($room_id);
        $this->deleteChipKey($room_id);
        $this->deleteSeenCardKey($room_id);
        $this->deleteRoomSequenceKey($room_id);
        $this->deleteRoomGuestKey($room_id);
        $this->deleteRoomKey($room_id);
        $this->deleteRoomRuleKey($room_id);
        Logger::getInstance()->info("[$room_id] 房间清理完毕!");
    }

    public function generalRoomPassword($room_id){
        mt_srand();
        $password = mt_rand(100000,999999);
        $pubredis = Redis::getInstance(0);
        $roomPasswordKey = strtr(Redis_Const::RoomPassword_Key,["[passowrd]"=>$password]);
        if ($this->redis()->get($roomPasswordKey)){
            $this->generalRoomPassword($room_id);
        }else{
            $this->redis()->setex($roomPasswordKey, 24*60*60, $room_id);
            return $password;
        }
    }

}